import {
  Controller,
  UseGuards,
  BadRequestException,
  Req,
  UseInterceptors,
  Post,
  Param,
  Body,
  UnauthorizedException,
  Delete,
  NotFoundException,
  Get,
} from '@nestjs/common';
import {
  Crud,
  CrudController,
  Override,
  ParsedRequest,
  ParsedBody,
  CrudRequest,
  CrudRequestInterceptor,
} from '@nestjsx/crud';
import { ApiTags, ApiBearerAuth, ApiConsumes } from '@nestjs/swagger';
import { deserializeArray, deserialize } from 'class-transformer';
import * as _ from 'lodash';
import { PlayList, Song } from '../entities';
import { PlayListsService, SongsService } from '../services';
import { AuthGuard } from '@/guards';
import { RequestDtoRelationTransformer } from '@/interfaces';
import { User } from '@/modules/users/entities/user.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';

@Crud({
  model: {
    type: PlayList,
  },
  query: {
    limit: 25,
    maxLimit: 100,
    join: {
      songs: {},
      belongsToUser: {},
    },
  },
  routes: {
    exclude: ['createManyBase'],
    createOneBase: {
      decorators: [UseGuards(AuthGuard), ApiBearerAuth()],
    },
    updateOneBase: {
      decorators: [UseGuards(AuthGuard), ApiBearerAuth()],
    },
    replaceOneBase: {
      decorators: [UseGuards(AuthGuard), ApiBearerAuth()],
    },
    deleteOneBase: {
      decorators: [UseGuards(AuthGuard), ApiBearerAuth()],
    },
  },
})
@ApiTags('play-lists')
@Controller('resources/play-lists')
// eslint-disable-next-line prettier/prettier
export class PlayListsController implements CrudController<PlayList>, RequestDtoRelationTransformer<PlayList> {
  constructor(
    public service: PlayListsService,
    public songsService: SongsService,
    @InjectRepository(PlayList)
    private readonly repo: Repository<PlayList>,
  ) {}

  get base(): CrudController<PlayList> {
    return this;
  }

  dtoRelationTransform(dto: PlayList) {
    try {
      if (typeof dto.tags === 'string') {
        dto.tags = JSON.parse(dto.tags);
      }
      if (typeof dto.songs === 'string') {
        dto.songs = deserializeArray(Song, dto.songs);
      }
      if (typeof dto.belongsToUser === 'string') {
        dto.belongsToUser = deserialize(User, dto.belongsToUser);
      }
    } catch (e) {
      if (e instanceof SyntaxError) {
        throw new BadRequestException(e.message, e.name);
      }
      throw new BadRequestException();
    }
    return dto;
  }

  @UseGuards(AuthGuard)
  @ApiBearerAuth()
  @ApiConsumes('multipart/form-data')
  @Override()
  createOne(
    @ParsedRequest() crudReq: CrudRequest,
    @ParsedBody() dto: PlayList,
    @Req() req,
  ) {
    dto = this.dtoRelationTransform(dto);
    dto.belongsToUser = req.user;

    return this.base.createOneBase(crudReq, dto);
  }

  @UseGuards(AuthGuard)
  @ApiBearerAuth()
  @ApiConsumes('multipart/form-data')
  @Override()
  updateOne(
    @ParsedRequest() crudReq: CrudRequest,
    @ParsedBody() dto: PlayList,
    @Req() req,
  ) {
    dto = this.dtoRelationTransform(dto);
    dto.belongsToUser = req.user;

    return this.base.updateOneBase(crudReq, dto);
  }

  @UseGuards(AuthGuard)
  @ApiBearerAuth()
  @ApiConsumes('multipart/form-data')
  @Override()
  replaceOne(
    @ParsedRequest() crudReq: CrudRequest,
    @ParsedBody() dto: PlayList,
    @Req() req,
  ) {
    dto = this.dtoRelationTransform(dto);
    dto.belongsToUser = req.user;

    return this.base.replaceOneBase(crudReq, dto);
  }

  /**
   * 歌单获取歌曲列表接口
   * @param id 歌单id
   * @param req request
   * @returns 歌曲列表
   */
  @UseGuards(AuthGuard)
  @Get(':id/songs')
  async getSongs(@Param('id') plId: number, @Req() req) {
    return await this.songsService.find({
      relations: ['inPlayLists'],
      where: qb => {
        qb.where('Song__inPlayLists.id = :id', { id: plId });
      },
    });
  }

  /**
   * 歌单添加歌曲接口
   * @param id 歌单id
   * @param songId 要添加的歌曲id
   * @param crudReq `CrudRequest`
   * @param req request
   * @returns 更新后的歌单
   */
  @UseGuards(AuthGuard)
  @UseInterceptors(CrudRequestInterceptor)
  @Post(':id/songs')
  async addSong(
    @Param('id') id: number,
    @Body('songId') songId: string,
    @ParsedRequest() crudReq: CrudRequest,
    @Req() req,
  ) {
    const playList = await this.service.findOne(id, {
      relations: ['belongsToUser', 'songs'],
    });

    // 检查歌单是否属于用户
    if (playList.belongsToUser.id !== req.user.id) {
      throw new UnauthorizedException();
    }

    // 获取请求歌曲
    const song = await this.songsService.findOne(songId);

    // 检查歌曲是否已在歌单中
    let existedIndex = -1;
    if (playList.songs.length > 0) {
      existedIndex = playList.songs.findIndex(
        song => song.id === Number.parseInt(songId),
      );
    }

    if (song && existedIndex === -1) {
      playList.songs.push(song);
      await this.base.updateOneBase(crudReq, playList);
      return song;
    }
  }

  /**
   * 歌单移除歌曲接口
   * @param id 歌单id
   * @param songId 要移除的歌曲id
   * @param crudReq `CrudRequest`
   * @param req request
   * @returns void
   */
  @UseGuards(AuthGuard)
  @Delete(':id/songs/:songId')
  async removeSong(
    @Param('id') id: number,
    @Param('songId') songId: number,
    @Req() req,
  ) {
    const playList = await this.service.findOne(id, {
      relations: ['belongsToUser', 'songs'],
    });

    // 检查歌单是否属于用户
    if (playList.belongsToUser.id !== req.user.id) {
      throw new UnauthorizedException();
    }

    // 检查歌曲是否已在歌单中
    let existedIndex = -1;
    if (playList.songs.length > 0) {
      existedIndex = playList.songs.findIndex(song => song.id === songId);
    }
    if (existedIndex === -1) {
      throw new NotFoundException();
    }

    _.pullAt(playList.songs, existedIndex);
    await this.repo.save(playList);
  }
}
